Repository navigation
Conversation
…e scene store divide_zone over the hosted MCP crashed with "Cannot read properties of undefined (reading 'getState')" (Hawkesbury run 2): the store's history step wrapped a host's runtime too, and the hosted server's store has no history. Main now applies a host's runtime without that step; this case keeps it so. Slice: S1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- add_wall, add_level, create_stair, place_items, create_room, furnish_room, search_assets and the fourteen room and floor tools (divide_zone, merge_zones, set_floor_foundation, create_mezzanine…) each have one contract and one core operation, registered on the MCP and defined by the chat from the same object. create_wall, create_level, place_item, create_stair_between_levels and cut_opening leave the MCP: add_wall, add_level, place_items, create_stair, add_door and add_window replace them. create_roof keeps its own definitions for now. - An operation that reads construction the host derives (re-derived rooms, auto ceilings, floor plates) returns afterReconcile; both hosts run it through applyAgentOutcome with their own reconciler, in one undo step. - A mutating shared tool says what it achieved: what the scene holds after the call, against a copy taken before it, so a host that writes its node map in place no longer reads every creation as unchanged. - add_object requires a reason, what the object stands in for, checked before its script runs; verify_scene lists authored objects with their reason. A plain box with a wall's size, or a floor plate, is refused. - The MCP takes the host's item library for search_assets, place_items and furnish_room; a standalone server keeps the built-in list. A search with no match points to add_object. - No listed input schema holds a tuple, so Claude Code keeps every tool (it had dropped nine room and floor tools), and shared tools declare loose outputs, so a field core adds no longer fails a client that listed them. Slice: S1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d opening) A cased opening or an arched passage, as the editor's door panel offers: add_door takes openingKind 'opening' and builds a door node with no leaf, framed or arched by its outline. Slice: S1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… in its way A build that deletes plan-only floors, then copies a floor up, pushed every floor above with each copy, gap included, so a model spent calls on reorder_level. A copy now fills the free floor; without a gap nothing changes, in the editor's Duplicate too. Slice: S1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run 3 (2026-10-05) wrote its front door's three glass strips off as "not possible": both tools take code, a script for what the fields cannot express, and their descriptions stopped at outline, type and style. They now say that glass strips, a pattern of lites or carved panels are written as a script in code, never left as not possible. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> (cherry picked from commit 923f64005ae8abc2a05977ed34ead326eec2d0c3)
Working parity, from the MCP ↔ chat runner's first report: - add_door and add_window are one core operation, addWallOpening, and one result the contract declares: ok, the id, wallId, localX, t, wallLength, clamped, coordinateSystem, sillHeight, message and achieved. The MCP's `position` copy of t is gone; the input keeps it. A rebuilt opening answers the same, rebuiltOpeningResult. - find_by_type refuses a level that is not there (level_not_found), as get_zones does; it answered "nothing found". - add_level on a scene with no building starts it as the editor does: a site (unless one stands), a building, its ground level. An agent that cleared the scene could not begin again with the tools. - create_project saves the session's scene as the project's first draft: from any other session it was scene_not_found until a save. - Every write says where it went, top-level: project, and `unsaved` with how to keep it on a scratch scene no project holds (writeTarget). A result's own `note` stays the operation's. Shared-table cases for find_by_type and the fresh start; the MCP's tests move from `position` to t and achieved. Slice: S1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run 3 took five attempts to paint a pier grey through patches:
`material: {color}` reported applied and stored {} (the schemas drop
unknown keys), `materialPreset: null` was refused (no way to clear), the
preset hid the material set under it, and an unknown preset rendered grey
with no word.
honestNodePatch, on apply_patch's updates: a path the node would drop or
change is refused naming it (unknown_field: material.color, where a colour
goes in material.properties.color), a material the library lacks is refused
naming the nearest of the target's kind (unknown_material), a material a
set preset hides is refused naming the preset (shadowed_field), and null
clears a field (field_required when the field is required). Free-form
records (metadata, slots) pass as sent.
Slice: S1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
S1's rename of place_item into place_items placed on a level's floor only. As main's place_item did, an item may now name its host (targetNodeId), its position in level coordinates as main took it: - a wall: along it from its start, y the height of the item's bottom (required), on the side of the wall the point is on (the front faces its left; the back is turned round), centred in the wall or, for a wall-side fixture, on its face; kept inside the span and the height, as the editor's wall placement keeps it; wallId, wallT and side set; - a ceiling: under it, its top flush; - an item standing on a floor: in its frame. An object built with add_object takes it on its real surface under the point, or hangs a ceiling item from its underside above it (a sloped vault), and the result names the part (restingOn); a catalog item, on its top; - a room, a slab or a level: its floor. Each item is refused on its own: height_required, item_too_tall, unsupported_host (a door), host_not_found, host_not_on_level (an item on an item). An id the library lacks stays refused (asset_not_found) rather than placed as main's 0.5 m placeholder. The surface logic is the chat's place_on_surface's, moved into core. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tions S1's create_stair dropped what main's create_stair_between_levels set: - railingMode (none, left, right, both; default both); - materialPreset, on the flight and its segment, refused when the library lacks it, naming the flooring it has; a stair takes no paint, so the colour it points to is a flat library one (library:preset-*). The lab's requireMaterialRef checks it, as apply_patch and update_node do: upstream's unknownMaterialPresetRefusal rides on its node-slots module, which the lab does not have yet; the two meet when the lab merges upstream; - name (default Staircase N); - the opening controls, under main's names: createDestinationSlabOpening, createSourceCeilingOpening, destinationSlabId, sourceCeilingId, openingWidth, openingLength, openingOffset, openingCenter and openingRotation (in degrees, as the flight's rotation). With none of them, the stair cuts its own opening as the editor's stair tool does (openingOffset is its margin, 0.08 m by default; both cuts off cuts none). With any, the opening is cut as given, as main cuts it: a floor opening owned by the stair, with the pose the live opening systems move it by, and the stair's own opening off so nothing is cut twice. Main centred that opening at z + length / 2 whatever the flight's turn; here it follows the climb, so a turned flight's opening stays over it. slab_not_found and ceiling_not_found refuse ids that name no slab or ceiling. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…read alike A Zod tuple renders as prefixItems in the chat's JSON schema and as a flattened array on the MCP, so agent-surface parity found the two input schemas apart. openingCenter is a length-2 number array, as every other contract writes an (x, z) point. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The pascal-3d workflows said place_items places on a level's floor only; it now takes a host per item (targetNodeId), as main's place_item did. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… default requireMaterialRef names a flat library colour (library:preset-*) unless its caller asks for paint, as paint does: honest patches and create_stair then point to no tool a slice may lack, and create_stair needs no flag. Slice: S1 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… it empty 2026-10-03, a Claude Code run on the hosted MCP: create_project in a session that had another project loaded returned that project's levels, and the next save would have written it into the new project. The session is now bound to the new project first, then, when it was bound to another one, its scene is emptied to the default site, building and level, with no undo back to the old one. Work not bound to any project is kept: it is what the new project is for. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… was not loaded from 2026-10-03, a Claude Code run on the hosted MCP: the server reloaded, the session started over on a blank scene, and save_scene(projectId) wrote it over the project's draft (8 levels, 10 imported plans, the calibration and the alignment, no history). save_scene now refuses scene_not_loaded when the session's scene was not loaded from (or created for) the target project or scene and that one holds more than the default site, building and level; replace: true writes over it on purpose. Live sync already writes only to the scene a session loaded. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Run 4's first save_scene, right after create_project bound the session to its project, was refused "projectId is required for Supabase store. Call create_project first." With no id and no projectId, save_scene now saves the current scene to the scene this session is bound to (create_project, load_scene or an earlier save); with nothing bound, it saves as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A floor copy is hundreds of ids the model never uses; written into the prompt cache once, they are read back on every call after. The result keeps the counts per type in `copied`, lists the first 40 ids and says how many it left out (`newNodeIdsOmitted`), which the MCP declares. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since owned floor openings, a stair's opening is a floor-opening node on the floor above, owned by the stair. The check already looks for it; these cases pin it: an owned opening on the floor above counts, one drawn on the stair's own ceiling does not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every write gives an `achieved` (what the scene holds after the call), but the MCP's add_object returned none; the chat's add_object did. The headless parity gate could not see it, its chat lane unreachable on that row; parity with an editor tab showed it. The MCP now counts the change against a copy of the scene before the call, as its shared tools and the chat do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run 4 (L66): the agent made the porch an outdoor room, so the house wall behind it had a room on both faces and knew no outside. The front door faced the hall, view_scene showed its outside from inside, and the porch face took the inside finish. A room is outdoor when it has no ceiling and can be left without crossing a wall, through a separator no room stands behind or into another such room, found as a fixed point. A wall face on an outdoor room is exterior, so door facing, view_scene, facades and finishes follow on both paths. A room that only lost its ceiling, a kitchen open to a terrace, and a courtyard walled on every side stay inside; a carport (no ceiling, an open side) is outside. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ywhere L59 replaced "blocks door clearance" with the item, its size and the door it is in the way of; the MCP test and the clearance log still read the old text, and the old reason entry was left unused. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…r too large for its room L59, run 4: furnish_room skipped the catalog's 2.34 m bathtub as blocking the bath's door; the agent then set it there itself with place_items, which checked only that its centre stood in a room, and verify_scene ended on two blocked doors. A floor item is now refused on its own when the room it stands in holds it in no turn (too_large_for_room, with both sizes, pointing to a smaller one or add_object at the room's size), or when it stands in the space a door needs, as verify_scene's blocked-door check sees it (blocks_door, naming the door and a spot in the room that clears every door, when there is one). Overlaps are not refused: a chair under its table and a bed on its rug overlap by design. Items on a host keep their own checks. furnish_room's skips name the item's size, the door in the way, or that the room is too small and what instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tant (L68) The store's new-scene level wrote a literal 2.5. It now writes DEFAULT_LEVEL_HEIGHT, as the hosted MCP's default scene and a new project's empty scene do, so the three cannot drift apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test asked for one roofing material by its id's prefix, which is one library's naming, not every library's. It now asserts roofing first as the library tags it (surfaces include roof), which every library with roofing passes; which roofing that is stays the data's business. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s what they protect
apply_patch held the honest-update check inline. A patch guard now
registers from its own module, run by order; apply_patch runs what is
registered and passes an McpError through as it is. The honest-update check
is the first, from honest-patch-guard.ts, and answers as it did:
{ code, patchIndex, id, message }.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A door's swing and a sectional door's track run behind its facing; add_door always faced the wall's front, so a garage drawn with its front inside ran its overhead track on the street, and a front door behind a porch faced the hall. add_door now faces a door out when the wall knows its outside (resolveWallExteriorSide): rotation π and side 'back' when the outside is the back. Walls that do not know their outside, and inside walls, keep the front as before. One rule for agents and people: the editor's door tool places a door on an outside wall facing out whichever face is hovered (placedDoorFace); R still flips it. The shared opening cases pin both wall directions and the inside wall. Doors made in the same call as their walls are not covered: their walls learn their sides only afterwards. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The outdoor-sides cases came with checks of a facade fill and a view's framing from the porch, tools this branch does not have. The porch case now checks what it owns: the wall is exterior on the porch face, and add_door places a door that faces the porch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ir winders) into the shared tools foundation Where main and this branch did the same thing, main's version stands: - measure_stair and fit_stair (pascalorg#1000) are shared tools next to create_stair: one stairs contract module and both operations registered. - verify_scene keeps main's informational issues (hasIssues ignores severity 'info') with this branch's authored objects and checks. - create_stair_between_levels stays replaced by create_stair; main's sizing of a new flight from the design targets (planStairCreation) is carried into create_stair in the next commit. - The tool lists (annotations, README, the skill) gain main's two tools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main sizes a new flight from the stair's design targets and resolves its rise against what it stands on and arrives at (planStairCreation, pascalorg#1000). create_stair now builds its flight the same way: the run and the risers come from the design targets unless given, and the stair keeps uniform risers and its flight's step count. main's two sizing cases now run on create_stair. The ring of a stair's owned opening may start at another corner once main re-plans it after a move; the case compares the ring's extent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…into the foundation Both sides kept: the foundation's furnish_room export and asset catalog, main's optional hosted service tools, registered before the resources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This is the by-hand counterpart to a refusal agents already get. Since #1007, The branch is stacked on #1007 and targets
Start with |
… labels it, a Separator Divide, merge, delete and an outdoor room's outline now say "a separator (the editor's Separator: a room boundary with no wall)", so an agent describes what the person finds in the Scene panel instead of an "open divider", the door and window panels' word for the bars between panes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment-only: each comment keeps the failure that motivated the code and drops which session found it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9b3f92c to
e1b9464
Compare
An object named after something Pascal builds is built with a hint naming the tool for it. Four of those hints named tools this server does not register (create_reference_elements, create_stairs_and_lifts, correct_plan_reading, apply_facade), so an agent following one called a tool that fails. They now name add_wall, create_room with its floor tools, add_door / add_window, create_stair and create_roof; lifts and balconies, which have no tool here, get no hint. A test checks every tool a hint names against tools/list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… falls in With opening controls, create_stair reported the storey's first slab and first ceiling as the surfaces it cut, and took any slab upstairs as proof a hole was cut there. On a floor of several rooms that named the wrong room's slab, and an opening under no slab was still reported cut. It now picks the slab above and the ceiling below that hold the opening's centre; a slab or ceiling id given still wins. main's create_stair_between_levels picked the first surface the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… above is cut
slabHoleCut counted every opening the flight cut, so a stair cutting only the
ceiling below reported a hole in the slab above, and an agent took the upper
floor for open when it was not. It is now true only for an opening drawn on
the floor above, as the chat's create_stair meant it on main ("auto floor
opening").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is warned while placed
place_items refuses a floor item standing in the space a door
needs, or one its room cannot hold in any turn, while the editor's item tool
checked only collisions. The check moves into core (floorItemFit), place_items
keeps its refusal words, and the editor reads the same function for a person:
a warning, not a block, in the HUD's "!" row while the item is placed or
moved ("Blocks the door to Bath", "Too large for Bath"), and at the top of the
selected item's panel with the sizes. Floor items only.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
e1b9464 to
986e784
Compare
… warning (pascalorg#1013) into the sync bundle Union with main's clear_scene (pascalorg#1012): its contract and registration sit beside the foundation's tools; live sync keeps the foundation's { status, project } answer and main's deliberate wipe path; apply_patch answers a store's refusal with its code, then rethrows MCP errors; the annotation count is computed from the policy, which lists clear_scene. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer guide: a floor item in a door's way is warned by hand too
Stacked on #1007 (the shared tools foundation). This PR targets
main, so GitHub lists #1007's commits as well. Review only the top commit: the comparison against #1007. The foundation and this follow-up merge together.Merge order (in pairs, each editor PR then its private PR): #1007, then pascalorg/private-editor#843, then this PR, then pascalorg/private-editor#852, which points its submodule here.
Why: with #1007,
place_itemsrefuses a floor item standing in the space a door needs (blocks_door), or one its room can't hold in any turn (too_large_for_room). The editor's item tool checked only collisions, so a person could do by hand what an agent is refused, with nothing said. This PR is the manual counterpart: one check in core, read by both.What changes for a user:
place_itemsrefuses the same cases with the same words as in core, mcp: the shared tools foundation, one definition per agent tool #1007. The check now lives in core (floorItemFit), and the editor reads the same function.How to test:
bun install && bun run cion this branch.bun dev). Draw a room about 3.1 × 2.2 m, name it Bath, and put a door in one wall.place_itemswith the tub across the door is refusedblocks_door, naming the door and a spot that clears it.What to read:
packages/core/src/building/floor-item-fit.ts:floorItemFit, the check, andfloorItemWarning, the sentence a person reads;packages/editor/src/hooks/use-floor-item-warning.ts: the warning read at the item's live pose while it's placed or moved.What to skim:
packages/core/src/agent-operations/place-items.ts: now callsfloorItemFit, with its refusal words unchanged;helper-manager.tsx,item-helper.tsx,registered-tool-helper.tsxandcontextual-helper-panel.tsx: the "!" row (notice), placed after main's fence-placement card;packages/nodes/src/item/panel.tsx: the warning at the panel's top.What to ignore:
floor-item-fit.test.ts.What does this PR do?
The manual counterpart of
place_items's door-clearance and room-size refusals:floorItemFit(core,building/) decides whether a floor item fits where it stands: one its room can't hold in any turn, or one in a door's keepout asverify_scene's blocked-door check sees it, doesn't fit. For a door, it suggests a spot in the room that clears every door.place_itemsrefuses with it, and its words are unchanged.floorItemWarningturns a misfit into the sentence a person reads. The editor shows it in the HUD's "!" row while the item is placed or moved, and at the top of the selected item's panel.No schema change; old scenes load as before.
How to test
bun install && bun run ci.bun test packages/core/src/building/floor-item-fit.test.ts packages/core/src/agent-operations: the editor andplace_itemsagree on the same spot; an item that fits gets no warning; an item on a table or a wall isn't treated as on the floor; a room with no name still reads as a sentence.Screenshots / screen recording
Not added yet.
Checklist
bun devbun checkto verify)mainbranch🤖 Generated with Claude Code
Note
Medium Risk
Large expansion of agent-facing scene mutation and validation paths; regressions would affect MCP/chat parity and what agents can place or refuse, though much is covered by exhaustive fixture-driven tests.
Overview
Expands the shared agent-tool contract so core, MCP, and chat run the same declarative case tables: new fixture modules for
add_object,create_room,furnish_room,place_items,search_assets, structure tools (add_wall,add_level,create_stair), room transforms, andfind_by_type, plus richer expectations (catalogcontext, customcheckcallbacks, surface-specific cases).Behavior wired in core includes unified operations for rooms (
create_roomwith edge-based doors/windows and reconcile-derived slab/ceiling ids), furnishing and catalog search, walls/levels/stairs (including owned floor openings and empty-sceneadd_levelbootstrap), andadd_objectrules: requiredreasonon create, refusal of plain wall/slab boxes, and hints when names overlap Pascal-built types.Scene plumbing gets store-aligned
applySceneChanges,mergeSceneChangesfor batched undo,applyAgentOutcome(apply + reconcile in tests),achievedChangesfor honest delete deltas on MCP/chat, truncatedduplicate_levelnode id payloads, stricterfind_by_type/honestNodePatch/requireMaterialRef,verify_sceneupdates for stair-owned openings and authored-object listing, and optional scene measure/checkpoint registration.Reviewed by Cursor Bugbot for commit 986e784. Bugbot is set up for automated code reviews on this repo. Configure here.